package cn.xiaoliu.basic.service.impl;

import cn.xiaoliu.basic.constant.LoginConstant;
import cn.xiaoliu.basic.dto.LoginDto;
import cn.xiaoliu.basic.exception.BusinessException;
import cn.xiaoliu.basic.service.ILoginService;
import cn.xiaoliu.basic.utils.AjaxResult;
import cn.xiaoliu.basic.utils.HttpClientUtils;
import cn.xiaoliu.basic.utils.MD5Utils;
import cn.xiaoliu.basic.utils.StrUtils;
import cn.xiaoliu.user.domain.Logininfo;
import cn.xiaoliu.user.domain.User;
import cn.xiaoliu.user.domain.WxUser;
import cn.xiaoliu.user.mapper.LogininfoMapper;
import cn.xiaoliu.user.mapper.UserMapper;
import cn.xiaoliu.user.mapper.WxUserMapper;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
public class LoginServiceImpl implements ILoginService {

    @Autowired
    private LogininfoMapper logininfoMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private WxUserMapper wxUserMapper;
    @Autowired
    private UserMapper userMapper;

    /**
     * 注册登录
     * @param dto
     * @return
     */
    @Override
    public Map<String,Object> account(LoginDto dto) {
        //一.校验
        //1.所有参数不能为空
        if (StringUtils.isEmpty(dto.getUsername())
                || StringUtils.isEmpty(dto.getPassword())
                || StringUtils.isEmpty(dto.getType())){
            throw new BusinessException("所有的信息都必须填写");
        }
        //二.根据username和type查询用户是否存在（查Logininfo表）
        Logininfo logininfo = logininfoMapper.loadByDto(dto);//type为0代表管理员，为1代表用户
        //1.如果不存在就报错
        if (logininfo == null){
            throw new BusinessException("用户名或者密码错误");
        }
        //2.如果存在，比对密码
        String salt = logininfo.getSalt();
        //登录的明文密码需要加密
        String md5Pwd = MD5Utils.encrypByMd5(dto.getPassword() + salt);
        if (!md5Pwd.equals(logininfo.getPassword())){
            //2.1密码不一致报错
            throw new BusinessException("用户名或者密码错误");
        }
        //2.2密码一致登录成功
        //三.登录成功过后
        //1.生成token随机串，然后存储redis，再设置30分钟过期
        String token = UUID.randomUUID().toString();//一定要toSting，UUID返回的是一个如果不toString返回的就是一个地址
        redisTemplate.opsForValue().set(
                token,
                logininfo,//登录成功的用户
                30,
                TimeUnit.MINUTES);//分钟
        //2.返回前端数据：返回token随机串和logininfo的信息
        Map<String, Object> map = new HashMap<>();
        map.put("token",token );
        //作为高级技师的我们，敏感信息应该置空隐藏起来
        logininfo.setSalt(null);
        logininfo.setPassword(null);
        map.put("logininfo",logininfo );//为了使前端登录成功后跳转到主页直接显示登录人
        return map;
    }

    /**
     * 微信登录
     * @param param
     */
    @Override
    public AjaxResult wechat(Map<String, String> param) {
        //校验
        String code = param.get("code");
        if (!StringUtils.hasLength(code)){
            throw new BusinessException("系统错误");
        }
        //携带code发送请求从微信获取携带token的地址
        String getTokenUrl = LoginConstant.WXUSER_GET_TOKEN_URL
                .replace("APPID",LoginConstant.APPID )
                .replace("SECRET", LoginConstant.SECRET)
                .replace("CODE",code );
        //拿到地址发送请求生成http地址（携带token的字符串）
        String tokenStr = HttpClientUtils.httpGet(getTokenUrl);
        //把http地址转成json对象
        JSONObject jsonObject = JSON.parseObject(tokenStr);
        //拿到对象中的token和openid
        //发送请求获取token和openId
        String accessToken = jsonObject.getString("access_token");
        String openId = jsonObject.getString("openid");
        //根据openid查询t_wxuser中是否存在，user_id是否有值
        WxUser wxUser = wxUserMapper.loadByOpenId(openId);
        //1.如果存在，并且user_id有值，直接免密登录
        if(wxUser!=null && wxUser.getUser_id()!=null){
            Logininfo logininfo = logininfoMapper.loadByUserId(wxUser.getUser_id());
            String token = UUID.randomUUID().toString();//一定要toSting，UUID返回的是一个如果不toString返回的就是一个地址
            redisTemplate.opsForValue().set(
                    token,
                    logininfo,//登录成功的用户
                    30,
                    TimeUnit.MINUTES);//分钟
            //2.返回前端数据：返回token随机串和logininfo的信息
            Map<String, Object> map = new HashMap<>();
            map.put("token",token );
            //作为高级技师的我们，敏感信息应该置空隐藏起来
            logininfo.setSalt(null);
            logininfo.setPassword(null);
            map.put("logininfo",logininfo );//为了使前端登录成功后跳转到主页直接显示登录人
            return AjaxResult.me().setResultObj(map);
        }else {
            //2.如果不存在，前端需要跳到绑定页面，需要用户自己绑定登录信息（注意需要将token和openid拼接返回）
            String getBinder = "accessToken="+accessToken+"&openId="+openId;
            return AjaxResult.me().setSuccess(false).setResultObj(getBinder);
        }

    }
    //微信绑定
    @Override
    public Map<String, Object> binder(Map<String, String> param) {
        //校验
        //所有参数不能为空
        String phone = param.get("phone");
        String verifyCode = param.get("verifyCode");
        String accessToken = param.get("accessToken");
        String openId = param.get("openId");
        if (StringUtils.isEmpty(phone)
                || StringUtils.isEmpty(verifyCode)
                || StringUtils.isEmpty(accessToken)
                || StringUtils.isEmpty(openId)){
            throw new BusinessException("参数异常");
        }
        //检验验证码是否过期，从redis里面拿检验
        Object yzmTmp = redisTemplate.opsForValue().get(LoginConstant.PHONE_BINDER + phone);
        //如果没有拿到报错
        if (yzmTmp == null){
            throw new BusinessException("验证码失效，请重新获取");
        }
        //拿到了，检查是否一致，不用区分大小写
        String yzm = yzmTmp.toString().split(":")[0];
        if (!verifyCode.equalsIgnoreCase(yzm)){
            throw new BusinessException("验证码输入有误");
        }

        //校验手机号是否被注册
        User user = userMapper.loadByPhone(phone);
        Logininfo logininfo = null;
        if (user!=null){
            //1.如果被注册，查询出来t_user和t_logininfo
            logininfo = logininfoMapper.loadById(user.getLogininfo_id());
        }else {
            //2.如果没有被注册，生成新的t_user和t_logininfo
            user = initUser(phone);
            logininfo = user2Logininfo(user);
            logininfoMapper.save(logininfo);
            user.setLogininfo_id(logininfo.getId());
            userMapper.save(user);

        }

        //根据token+openId查询wxuser信息级联设置user_id并保存t_wxuser
        String getWxuserUrl = LoginConstant.WXUSER_GET_URL
                .replace("ACCESS_TOKEN", accessToken)
                .replace("OPENID", openId);
        String wxUserStr = HttpClientUtils.httpGet(getWxuserUrl);
        WxUser wxUser = JSON.parseObject(wxUserStr, WxUser.class);
        wxUser.setUser_id(user.getId());//绑定userid
        wxUserMapper.save(wxUser);
        //免密登录
        String token = UUID.randomUUID().toString();//一定要toSting，UUID返回的是一个如果不toString返回的就是一个地址
        redisTemplate.opsForValue().set(
                token,
                logininfo,//登录成功的用户
                30,
                TimeUnit.MINUTES);//分钟
        //2.返回前端数据：返回token随机串和logininfo的信息
        Map<String, Object> map = new HashMap<>();
        map.put("token",token );
        //作为高级技师的我们，敏感信息应该置空隐藏起来
        logininfo.setSalt(null);
        logininfo.setPassword(null);
        map.put("logininfo",logininfo );//为了使前端登录成功后跳转到主页直接显示登录人
        return map;
    }

    private Logininfo user2Logininfo(User user) {
        Logininfo logininfo = new Logininfo();
        //按照相同的名字拷贝属性
        BeanUtils.copyProperties(user,logininfo );
        logininfo.setType(1);//注意（一定要设置）前端用户注册用1
        return logininfo;
    }

    private User initUser(String phone) {
        User user = new User();
        user.setUsername(phone);
        user.setPhone(phone);
        //盐值
        String salt = StrUtils.getComplexRandomString(32);
        //密码 这里我们应该发短信告诉他默认密码是什么
        String md5Pwd = MD5Utils.encrypByMd5("6" + salt);
        user.setSalt(salt);
        user.setPassword(md5Pwd);
        return user;
    }
}
